//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//     Filename: CRXMLTranslator.h                                          //
//  Create date: December 13, 2004                                          //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  Description: This file describes the CRXMLTranslator class.  This class //
//                is a protocol translator that translates from the RTG     //
//                Roulette protocol to XML and back.                        //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2004 - Realtime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <stdio.h>
#include "lib.h"
#include "CRXMLTranslator.h"
#include "XMLDoc.h"
#include "endian.h"
#include "log.h"
#include "card.h"

//////////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////////
CRXMLTranslator::CRXMLTranslator( Server* pServer, Connection* pParent ) : 
                                             XMLTranslator( pServer, pParent, 10, 2 )
{
}

//////////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////////
CRXMLTranslator::~CRXMLTranslator()
{
}

/////////////////////////////////////////////////////////////////////////////
// TranslateIncomingMessage
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateIncomingMessage( uint8* pGameInBuff,  
												 uint32 nGameInBuffSize, 
												 uint8* pOutBuff, 
												 uint32 nOutBuffSize )
{
    bool8 bRetVal = false;

	// Handle the messages.  Note, I'm only going to look at the first character
	//  in a small attempt to optimize a bit
	switch( m_sMsgID[0] )
	{
		case 'L':
			bRetVal = TranslateInLogin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'B':
			bRetVal = TranslateInBalance( m_pDoc, pGameInBuff, nGameInBuffSize, GetNumBytesMsgLen() );
			break;

		case 'R':
			bRetVal = TranslateInRoll( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		default:
			String sMsg = TEXT("Error, received unknown client message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
			break;
	}

	// Report Error
	if( !bRetVal )
	{
		if ( m_sMsgID[0] == 'L' )
		{
			// Set an error into the out buffer
			ReturnLoginError( pOutBuff, nOutBuffSize );
		}
		else
		{
			// Set an error into the out buffer
			ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );

			String sMsg = TEXT("Error translating incoming message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
		}
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutgoingMessage
//////////////////////////////////////////////////////////////////////////////
uint32 CRXMLTranslator::TranslateOutgoingMessage( uint8* pOutBuff, 
												   uint32 nOutBuffSize, 
												   uint8* pGameOutBuff, 
												   uint32 nGameOutBuffSize, 
												   uint32 nTotalGameBytes    )
{
	bool8 bResult = true;
	uint32 nGameBytesUsed = 0;
	uint32 nOutBytesUsed = 0;

	while ( bResult && (nGameBytesUsed < nTotalGameBytes) )
	{
		// Keep track of buffers as we might be processing more than one game
		//   message at a time.
		uint8* pCurGameBuf     = pGameOutBuff     + nGameBytesUsed;
		uint32 nCurGameBufSize = nGameOutBuffSize - nGameBytesUsed;
		uint8* pCurOutBuf      = pOutBuff         + nOutBytesUsed;
		uint32 nCurOutBufSize  = nOutBuffSize     - nOutBytesUsed;

		// Make sure buffer has not been overrun!
		if ( nOutBytesUsed > nOutBuffSize )
		{
			ASSERT(false);
			Log::msgError( "Error, out buffer overflow!" );
			return 0;
		}

		// Now the actual message handlers!
		switch( pCurGameBuf[2] )
		{

			case 'L':
				bResult = TranslateOutLogin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'm':
				bResult = TranslateOutBalance( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'R':
				bResult = TranslateOutRoll( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'E':
				bResult = TranslateGenericOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case '!':	
			case 'B':
			case '$':
			case 'N':
				bResult = TranslateOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			default:
				bResult = ReturnErrorMessage( TEXT("UnknownMessage"), pCurOutBuf, nCurOutBufSize );
				Log::msgError( "Received unknown game message: %c", pCurGameBuf[1] );
				break;
		}

		// Check and see if any errors occurred
		if ( !bResult )
		{
			ReturnErrorMessage( TEXT("ParseError"), pCurOutBuf, nCurOutBufSize );
			Log::msgError( "Error translating outgoing message: %c", pCurGameBuf[1] );
		}

		// We know that the size of the message is always stored in the first
		//  byte of the client message.
		nGameBytesUsed +=  *( (uint16*)pCurGameBuf );

		// Out XML Messages are supposed to be NULL Terminated
		nOutBytesUsed += strlen( (char*) pCurOutBuf );
		nOutBytesUsed++;  // Add one for the NULL

		DEBUGMSG( ("\n***Outgoing: %s", pCurOutBuf) );
	}

	return nOutBytesUsed;
}

//****************************************************************************
// Incoming
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateInLogin
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateInLogin( XMLDoc* pDoc, uint8* pGameInBuff, uint32 nGameInBuffSize )
{
    ////////////////////////////////////////////////////////////////
	// Login the user
	////////////////////////////////////////////////////////////////
    if ( !LoginUser( pDoc ) )
    {
        return false;
    }

    // Start by skipping the first 2 bytes of the message, that will be the
	//  length
	uint32 nOffset = 2;

	////////////////////////////////////////////////////////////////
	// Write Login for Craps
	////////////////////////////////////////////////////////////////
	pGameInBuff[nOffset++] = 'M';
	pGameInBuff[nOffset++] = 'L';
	pGameInBuff[nOffset++] = 'C';
	pGameInBuff[nOffset++] = 'R';

	////////////////////////////////////////////////////////////////
	// Add Version Info
	////////////////////////////////////////////////////////////////
	if ( !AddLoginVersionInfo( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add ID
	////////////////////////////////////////////////////////////////
	if ( !AddLoginID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}
    	
	////////////////////////////////////////////////////////////////
	// play for money/play for fun
	////////////////////////////////////////////////////////////////
	if ( !AddLoginMoneyType( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Cient type: CSN-7534
	////////////////////////////////////////////////////////////////
	if ( !AddClientID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Authentication token. CSN-7075: New Authentication
	////////////////////////////////////////////////////////////////
	if ( !AddAuthenticationToken( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}
	
	////////////////////////////////////////////////////////////////
	// Finally, add message size
	////////////////////////////////////////////////////////////////
	pGameInBuff[0] = (uint8)nOffset;
	pGameInBuff[1] = 0;

	return true;
}


//////////////////////////////////////////////////////////////////////////////
// TranslateInRoll
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateInRoll( XMLDoc* pDoc, uint8* pGameInBuff, uint32 nGameInBuffSize )
{
	// get Number of bets
	uint8 nBets;
	if ( !pDoc->GetSingleNodeByte( TEXT("//num_bets"), nBets ) )
    {
		return false;
    }

	// now we can write Message size (2 bytes)
	Endian::SendInt2((uint16)(4+(nBets*5)), pGameInBuff );
	uint32 nOffset = 2;

	// Message ID
	pGameInBuff[nOffset++] = 'R';

	// number of bets
	pGameInBuff[nOffset++] = nBets;

	// translate bet/betID pairs
	for( int i=0; i<nBets; i++ )
	{
		String sBet,sID;
		sBet.sprintf( TEXT("//wager_%d"), i );
		sID.sprintf( TEXT("//wager_id_%d"), i );

		// bet
		uint32 nBet;
		if ( !pDoc->GetSingleNodeLong( sBet, nBet ) )
        {
			return false;
        }

		Endian::SendInt4( nBet, pGameInBuff + nOffset );
		nOffset +=4 ;

		// betID
		uint8 nBetID;
		if ( !pDoc->GetSingleNodeByte( sID, nBetID ) )
        {
			return false;
        }

		pGameInBuff[nOffset++] = nBetID;
	}

	return true;
}



//****************************************************************************
// Outgoing
//****************************************************************************

//----------------------------------------------------------
//				Helper function
//----------------------------------------------------------
bool8 TranslateLimits(	XMLDoc* doc, uint8* pBuff,	uint32& nOffset,
						const String& sMin,	const String& sMax )
{
	int32 nValue;

	Endian::ReceiveInt4( &nValue, pBuff+nOffset );
	if ( !doc->AddRootChildNode( sMin, (uint32) nValue ) )
    {
		return false;
    }

	nOffset += 4;
	Endian::ReceiveInt4( &nValue, pBuff+nOffset );
	if ( !doc->AddRootChildNode( sMax, (uint32) nValue ) )
    {
		return false;
    }

	nOffset += 4;
	return true;
}


//////////////////////////////////////////////////////////////////////////////
// TranslateOutError
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateOutError( uint8* pOutBuff, 
										  uint32 nOutBuffSize, 
										  uint8* pGameOutBuff, 
										  uint32 nGameOutBuffSize )
{
	bool8 bRetVal = false;

	if ( pGameOutBuff[2] == '!' )
	{
		bRetVal = ReturnErrorMessage( TEXT("Shutdown"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'B' )
	{
		bRetVal = ReturnErrorMessage( TEXT("Invalid bet"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == '$' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InsufficientFunds"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'N' )
	{
        // kharmon.  02-21-07.  MCA #29819.  Need to send back different messages for auto and admin logoffs.
        String strErrorName = "InvalidAction";
        int16 nLen = 0;
        Endian::ReceiveInt2( &nLen, pGameOutBuff );

        if(nLen==7)  // The extra 4 bytes have the error code.
        {
            int32 nErrorCode=0;
            Endian::ReceiveInt4( &nErrorCode, &pGameOutBuff[3] );

            switch(nErrorCode)
            {
                case TrackSQL::AdminLogout:
                {
                    strErrorName = "AdminLogout";
                    break;
                }
                case TrackSQL::AutoLogout:
                {
                    strErrorName = "TimedOut";
                    break;
                }
            }            
        }

        bRetVal = ReturnErrorMessage( strErrorName, pOutBuff, nOutBuffSize );
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutLogin
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateOutLogin( uint8* pOutBuff, 
										   uint32 nOutBuffSize, 
										   uint8* pGameOutBuff, 
										   uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
    {
		return false;
    }

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
    {
		return false;
    }

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Login") ) )
    {
		return false;
    }

	// Keep track of offset in buffer
	uint32 nOffset = 3;

	// Balance 
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;

	// Pass Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_pass"), TEXT("max_pass") ) )
    {
		return false;
    }

	// Odds Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_odds"), TEXT("max_odds") ) )
    {
		return false;
    }

	// Place Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_place"), TEXT("max_place") ) )
    {
		return false;
    }

	// Hardway/Propositions  Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_hp"), TEXT("max_hp") ) )
    {
		return false;
    }

	// Field/Big  Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_fb"), TEXT("max_fb") ) )
    {
		return false;
    }

	// Table Bet limits
	if( !TranslateLimits( &doc, pGameOutBuff, nOffset, TEXT("min_tab"), TEXT("max_tab") ) )
    {
		return false;
    }

	// Bet Odds
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("bet_odds"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;

	// Field 120 odds
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("fld_odds"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;

	// Save Game?
	uint8 nByteVal = *(pGameOutBuff + nOffset++);

	// If we have a saved game, we need to add  "saved_game" node
	if ( nByteVal == 1 )
	{
		String sg =  TEXT("saved_game");

		if ( !doc.AddRootChildNode(sg) )
        {
			return false;
        }
		String sQuery = TEXT("//");
		sQuery += sg;


		// Point
		nByteVal = *(pGameOutBuff + nOffset++);
		if ( !doc.AddChildNode( sQuery, TEXT("point"), (uint32)nByteVal ) )
        {
			return false;
        }

		// Die 1
		nByteVal = *(pGameOutBuff + nOffset++);
		if ( !doc.AddChildNode( sQuery, TEXT("die1"), (uint32)nByteVal ) )
        {
			return false;
        }

		// Die 2
		nByteVal = *(pGameOutBuff + nOffset++);
		if ( !doc.AddChildNode( sQuery, TEXT("die2"), (uint32)nByteVal ) )
        {
			return false;
        }

		// number of bets
		nByteVal = *(pGameOutBuff + nOffset++);
		if ( !doc.AddChildNode( sQuery, TEXT("num_bets"), (uint32)nByteVal ) )
        {
			return false;
        }

		for( int i=0; i<nByteVal; i++ )
		{
			String sTag;
			uint8 id = *(pGameOutBuff + nOffset++);					// wager ID
			Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );	// wager amount
			nOffset += 4;

			sTag.sprintf( TEXT("wager_id_%d"), i );
			if ( !doc.AddChildNode( sQuery, sTag, (uint32)id ) )
            {
				return false;
            }

			sTag.sprintf( TEXT("wager_%d"), i );
			if ( !doc.AddChildNode( sQuery, sTag, (uint32)nValue ) )
            {
				return false;
            }
		}

	}	// end of game saved game

	if ( !AddCurrencyOutput(&doc) )	// CSN-11204: return currency
	{
		return false;
	}
							
	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutRoll
//////////////////////////////////////////////////////////////////////////////
bool8 CRXMLTranslator::TranslateOutRoll( uint8* pOutBuff, 
									      uint32 nOutBuffSize, 
										  uint8* pGameOutBuff, 
										  uint32 nGameOutBuffSize )
{
	XMLDoc doc;
	int32 nValue;

	if ( !doc.StartNew() )
    {
		return false;
    }

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
    {
		return false;
    }

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Roll") ) )
    {
		return false;
    }

	// Keep track of offset in buffer
	uint32 nOffset = 3;

	// Balance 
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;

	// Payout 
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("payout"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;

	// Total of winning bets
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("win_bets"), (uint32) nValue ) )
    {
		return false;
    }
	nOffset += 4;


	// number of bets
	uint8 nByteVal = *(pGameOutBuff + nOffset++);
	if ( !doc.AddRootChildNode( TEXT("num_bets"), (uint32)nByteVal ) )
    {
		return false;
    }

	for( int i=0; i<nByteVal; i++ )
	{
		String sTag;
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );	// wager amount
		nOffset += 4;

		sTag.sprintf( TEXT("wager_%d"), i );
		if ( !doc.AddRootChildNode( sTag, (uint32)nValue ) )
        {
			return false;
        }
	}


	// Point
	nByteVal = *(pGameOutBuff + nOffset++);
	if ( !doc.AddRootChildNode( TEXT("point"), (uint32)nByteVal ) )
    {
		return false;
    }

	// Die 1
	nByteVal = *(pGameOutBuff + nOffset++);
	if ( !doc.AddRootChildNode( TEXT("die1"), (uint32)nByteVal ) )
    {
		return false;
    }

	// Die 2
	nByteVal = *(pGameOutBuff + nOffset++);
	if ( !doc.AddRootChildNode( TEXT("die2"), (uint32)nByteVal ) )
    {
        return false;
    }

	// Done - send the actual XML
	return doc.SendXML( pOutBuff, nOutBuffSize );
}
